import socket

# ### 通讯协议解析部分
BIT, WORD, DWORD = (1, 16, 32)


class MCCodeDefines:
    NormalFinish = "正常结束"
    AbnormalFinish = "异常结束"
    ControlCodeError = "控制代码校验错误"
    BlockIDError = "块号校验错误"
    FrameIDError = "帧识别编号校验错误"
    AccessPathError = "访问路径校验错误"
    CheckSumError = "和校验错误"
    FinishCodeError = "结束代码异常"
    NoEnterCodeError = "缺少回车换行字符"
    SubHeaderError = "副帧头代码错误"
    DataLengthError = "数据长度错误"


def calculate_check_sum(arr):
    _sum = 0
    for i in arr:
        _sum = _sum + i
    sum_check = _sum & 0xFF
    return sum_check


def ascii_to_value(msg, length):
    ret = []
    if length == BIT:
        for d in msg:
            ret.append(0 if d == 0x30 else 1)
        return ret
    else:
        buf_size = int(len(msg) / 4)
        for d in range(buf_size):
            ret.append(int(msg[d * 4: (d + 1) * 4], 16))
        if length == WORD:
            return tuple(ret)
        elif length == DWORD:
            return tuple([ret[i * 2] + (ret[i * 2 + 1] << 16) for i in range(int(len(ret) / 2))])
        else:
            raise ValueError("The message length type must in BIT, WORD or DWORD!")


# 四种MC协议帧
class MCFrameType:
    code4C = b'F8'
    code3C = b'F9'
    code2C = b'FB'
    code1C = b''


# 不同通讯帧的操作规范接口
class FrameOptions:
    def __init__(self, read_words_order, read_bits_order,
                 write_words_order, write_bits_order,
                 reg_x, reg_y, reg_m, reg_d, reg_l, reg_w, reg_r, reg_f,
                 block=None, identity=None, access_path=None):
        self.block = block
        self.identity = identity
        self.access_path = access_path
        self.finish_code = FinishCode(self.identity)

        self.read_words_order = read_words_order
        self.read_bits_order = read_bits_order
        self.write_words_order = write_words_order
        self.write_bits_order = write_bits_order
        self.X = reg_x
        self.Y = reg_y
        self.M = reg_m
        self.D = reg_d
        self.L = reg_l
        self.W = reg_w
        self.R = reg_r
        self.F = reg_f

    def read_x(self, addr, size):
        return self.read_bits_order + self.X + '{:06X}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_y(self, addr, size):
        return self.read_bits_order + self.Y + '{:06X}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_m(self, addr, size):
        return self.read_bits_order + self.M + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_d(self, addr, size):
        return self.read_words_order + self.D + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_l(self, addr, size):
        return self.read_bits_order + self.L + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_w(self, addr, size):
        return self.read_words_order + self.W + '{:06X}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_r(self, addr, size):
        return self.read_words_order + self.R + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_f(self, addr, size):
        return self.read_bits_order + self.F + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def write_y(self, addr, size, value: list):
        write_data = b''
        for v in value:
            if v is True:
                write_data += b'1'
            else:
                write_data += b'0'
        return self.write_bits_order + '{:06x}'.format(addr).encode('ascii') + '{:04X}' \
            .format(size).encode('ascii') + write_data

    def write_m(self, addr, value: list):
        write_data = b''
        for v in value:
            if v is True:
                write_data += b'1'
            else:
                write_data += b'0'
        return self.write_bits_order + '{:06d}'.format(addr).encode('ascii') + write_data

    def write_d(self, addr, value: list):
        write_data = '{:04d}'.format(len(value)).encode('ascii')
        for v in value:
            write_data += '{:04X}'.format(v).encode('ascii')
        return self.write_words_order + '{:06d}'.format(addr).encode('ascii') + write_data


class FrameOptionQ4C(FrameOptions):
    def __init__(self, slave_id, net_id, controller_id,
                 target_io_id_high, target_io_id_low, target_id, visitor_id):
        super().__init__(
            read_words_order=b'04010000',
            read_bits_order=b'04010001',
            write_words_order=b'14010000',
            write_bits_order=b'14010001',
            reg_x=b'X*', reg_y=b'Y*', reg_m=b'M*', reg_d=b'D*',
            reg_l=b'L*', reg_w=b'W*', reg_r=b'R*', reg_f=b'F*',
            identity=MCFrameType.code4C,
            access_path='{:02X}'.format(slave_id).encode('ascii')
                        + '{:02X}'.format(net_id).encode('ascii')
                        + '{:02X}'.format(controller_id).encode('ascii')
                        + '{:02X}'.format(target_io_id_high).encode('ascii')
                        + '{:02X}'.format(target_io_id_low).encode('ascii')
                        + '{:02X}'.format(target_id).encode('ascii')
                        + '{:02X}'.format(visitor_id).encode('ascii')
        )


class FrameOptionQ3C(FrameOptions):
    def __init__(self, slave_id=0, net_id=0, controller_id=0xFF, visitor_id=0):
        super().__init__(
            read_words_order=b'04010000',
            read_bits_order=b'04010001',
            write_words_order=b'14010000',
            write_bits_order=b'14010001',
            reg_x=b'X*', reg_y=b'Y*', reg_m=b'M*', reg_d=b'D*',
            reg_l=b'L*', reg_w=b'W*', reg_r=b'R*', reg_f=b'F*',
            identity=MCFrameType.code4C,
            access_path='{:02X}'.format(slave_id).encode('ascii')
                        + '{:02X}'.format(net_id).encode('ascii')
                        + '{:02X}'.format(controller_id).encode('ascii')
                        + '{:02X}'.format(visitor_id).encode('ascii')
        )


class FrameOptionQ2C(FrameOptions):
    def __init__(self, slave_id=0, visitor_id=0):
        super().__init__(
            read_bits_order=b'1',
            read_words_order=b'2',
            write_bits_order=b'3',
            write_words_order=b'4',
            reg_x=b'X*', reg_y=b'Y*', reg_m=b'M*', reg_d=b'D*',
            reg_l=b'L*', reg_w=b'W*', reg_r=b'R*', reg_f=b'F*',
            identity=MCFrameType.code4C,
            access_path='{:02X}'.format(slave_id).encode('ascii')
                        + '{:02X}'.format(visitor_id).encode('ascii')
        )


class FrameOptionA1C(FrameOptions):
    def __init__(self, slave_id=0, controller_id=0xFF):
        super().__init__(
            read_bits_order=b'BR',
            read_words_order=b'WR',
            write_bits_order=b'BW',
            write_words_order=b'WW',
            reg_x=b'X', reg_y=b'Y', reg_m=b'M', reg_d=b'D',
            reg_l=b'L*', reg_w=b'W*', reg_r=b'R*', reg_f=b'F*',
            identity=MCFrameType.code4C,
            access_path='{:02X}'.format(slave_id).encode('ascii')
                        + '{:02X}'.format(controller_id).encode('ascii')
        )


# 结束代码生成类
class FinishCode:
    def __init__(self, frame):
        if frame in (MCFrameType.code4C, MCFrameType.code3C, MCFrameType.code2C):
            self.len = 4
            self.ACK = b'QACK'
            self.NAK = b'QNAK'
        elif frame == MCFrameType.code1C:
            self.len = 2
            self.ACK = b'GG'
            self.NAK = b'NN'


# 设备操作的返回结果的规范接口
class OperateResult:
    is_success = False
    code = None
    data = None

    def __repr__(self):
        return f"Operation {'successfully' if self.is_success else 'failure'}: " \
               f"information is {self.code}\n" \
               f"analysis data is {self.data}"


# 四种格式的模型生成器
class MCForm1:
    def __init__(self, frame_opt: FrameOptions, check=False):
        self.frame_opt = frame_opt
        if check:
            self.check_sum = lambda arr: '{:02X}'.format(calculate_check_sum(arr)).encode('ascii')
        else:
            self.check_sum = lambda arr: False

    def request_msg(self, operation):
        msg = self.frame_opt.identity + self.frame_opt.access_path + operation
        return b'\x05' + msg + (self.check_sum(msg) or b'')

    def analyze_msg(self, msg, length):
        result = OperateResult()
        res_code = msg[:1]
        if res_code in (b'\x02', b'\x06', b'\x15'):
            msg = msg[1:]

            # 校验帧识别码
            if msg[:len(self.frame_opt.identity)] != self.frame_opt.identity:
                result.code = MCCodeDefines.FrameIDError
                return result
            msg = msg[len(self.frame_opt.identity):]

            # 校验访问路径
            if msg[:len(self.frame_opt.access_path)] != self.frame_opt.access_path:
                result.code = MCCodeDefines.AccessPathError
                return result
            msg = msg[len(self.frame_opt.access_path):]

            # 存在响应数据时解析响应数据
            if res_code == b'\x02':
                check_sum = self.check_sum(msg[:-2])

                if check_sum is False:
                    if msg[-1:] != b'\x03':
                        result.code = MCCodeDefines.ControlCodeError
                        return result
                    msg = msg[:-1]
                else:
                    if msg[-3:-2] != b'\x03':
                        result.code = MCCodeDefines.ControlCodeError
                        return result
                    if msg[-2:] != check_sum:
                        result.code = MCCodeDefines.CheckSumError
                        return result

                    msg = msg[:-3]
                # 数据解析
                result.data = ascii_to_value(msg, length)
                result.is_success = True
                return result

            elif res_code == b'\x06':
                result.is_success = True
                return result

            elif res_code == b'\x15':
                result.data = MCCodeDefines.AbnormalFinish
                result.data = int(msg, 16)
                return result

        else:
            result.code = MCCodeDefines.ControlCodeError
            return result


class MCForm2:
    def __init__(self, frame_opt: FrameOptions, check=False):
        self.frame_opt = frame_opt
        if check:
            self.check_sum = lambda arr: '{:02X}'.format(calculate_check_sum(arr)).encode('ascii')
        else:
            self.check_sum = lambda arr: False

    def request_msg(self, operation):
        msg = self.frame_opt.block + self.frame_opt.identity + self.frame_opt.access_path + operation
        return b'\x05' + msg + (self.check_sum(msg) or b'')

    def analyze_msg(self, msg, length):
        result = OperateResult()
        res_code = msg[:1]
        if res_code in (b'\x02', b'\x06', b'\x15'):
            msg = msg[1:]

            # 校验块号
            if msg[:len(self.frame_opt.block)] != self.frame_opt.block:
                result.code = MCCodeDefines.BlockIDError
                return result
            msg = msg[len(self.frame_opt.block):]
            # 校验帧识别码
            if msg[:len(self.frame_opt.identity)] != self.frame_opt.identity:
                result.code = MCCodeDefines.FrameIDError
                return result
            msg = msg[len(self.frame_opt.identity):]

            # 校验访问路径
            if msg[:len(self.frame_opt.access_path)] != self.frame_opt.access_path:
                result.code = MCCodeDefines.AccessPathError
                return result
            msg = msg[len(self.frame_opt.access_path):]

            # 存在响应数据时解析响应数据
            if res_code == b'\x02':
                check_sum = self.check_sum(msg[:-2])

                if check_sum is False:
                    if msg[-1:] != b'\x03':
                        result.code = MCCodeDefines.ControlCodeError
                        return result
                    msg = msg[:-1]
                else:
                    if msg[-3:-2] != b'\x03':
                        result.code = MCCodeDefines.ControlCodeError
                        return result
                    if msg[-2:] != check_sum:
                        result.code = MCCodeDefines.CheckSumError
                        return result
                    msg = msg[:-3]

                # 组合数据为列表进行返回
                result.is_success = True
                result.code = MCCodeDefines.NormalFinish
                result.data = ascii_to_value(msg, length)
                return result

            elif res_code == b'\x06':
                result.is_success = True
                result.code = MCCodeDefines.NormalFinish
                return result

            elif res_code == b'\x15':
                result.code = int(msg, 16)
                return result

        else:
            result.code = MCCodeDefines.ControlCodeError
            return result


class MCForm3:
    def __init__(self, frame_opt: FrameOptions, check=False):
        self.frame_opt = frame_opt
        if check:
            self.check_sum = lambda arr: '{:02X}'.format(calculate_check_sum(arr)).encode('ascii')
        else:
            self.check_sum = lambda arr: False

    def request_msg(self, operation):
        msg = self.frame_opt.identity + self.frame_opt.access_path + operation
        return b'\x02' + msg + (self.check_sum(msg) or b'')

    def analyze_msg(self, msg, length):
        result = OperateResult()
        res_code = msg[:1]
        if res_code == b'\x02':
            msg = msg[1:]
            # 记录被校验的数据字段
            checked_msg = msg[:-2]

            # 校验帧识别码
            if msg[:len(self.frame_opt.identity)] != self.frame_opt.identity:
                result.code = MCCodeDefines.FrameIDError
                return result
            msg = msg[len(self.frame_opt.identity):]

            # 校验访问路径
            if msg[:len(self.frame_opt.access_path)] != self.frame_opt.access_path:
                result.code = MCCodeDefines.AccessPathError
                return result
            msg = msg[len(self.frame_opt.access_path):]

            # 查询结束控制代码的位置，判断消息是否有响应数据
            if msg[-1:] == b'\x03':
                msg = msg[:-1]
                # 查询结束代码
                finish_code = msg[:self.frame_opt.finish_code.len]
                if finish_code == self.frame_opt.finish_code.NAK:
                    result.code = MCCodeDefines.AbnormalFinish
                    result.data = int(msg[self.frame_opt.finish_code.len:], 16)
                    return result
                elif finish_code == self.frame_opt.finish_code.ACK:
                    result.is_success = True
                    result.code = MCCodeDefines.NormalFinish
                    return result
                else:
                    result.code = MCCodeDefines.FinishCodeError
                    return result
            else:
                msg = msg[:-1]
                finish_code = msg[:self.frame_opt.finish_code.len]
                # 校验结束代码
                if finish_code == self.frame_opt.finish_code.ACK:
                    # 和校验
                    if msg[-2:] != self.check_sum(checked_msg):
                        result.code = MCCodeDefines.CheckSumError
                        return result
                    # 检查控制代码
                    if msg[-3:-2] != b'\x03':
                        result.code = MCCodeDefines.ControlCodeError
                        return result
                    msg = msg[:-3]
                    # 存在响应数据时进行解析
                    result.is_success = True
                    result.code = MCCodeDefines.NormalFinish
                    result.data = ascii_to_value(msg, length)
                    return result
                else:
                    result.code = MCCodeDefines.AbnormalFinish
                    return result


class MCForm4:
    def __init__(self, frame_opt: FrameOptions, check=False):
        self.frame_opt = frame_opt
        if check:
            self.check_sum = lambda arr: '{:02X}'.format(calculate_check_sum(arr)).encode('ascii')
        else:
            self.check_sum = lambda arr: False

    def request_msg(self, operation):
        msg = self.frame_opt.identity + self.frame_opt.access_path + operation
        return b'\x05' + msg + (self.check_sum(msg) or b'') + b'\r\n'

    def analyze_msg(self, msg, length):
        result = OperateResult()
        res_code = msg[:1]
        if res_code in (b'\x02', b'\x06', b'\x15'):
            if msg[-2:] != b'\r\n':
                result.code = MCCodeDefines.NoEnterCodeError
                return result

            msg = msg[1:-2]

            # 校验帧识别码
            if msg[:len(self.frame_opt.identity)] != self.frame_opt.identity:
                result.code = MCCodeDefines.FrameIDError
                return result
            msg = msg[len(self.frame_opt.identity):]

            # 校验访问路径
            if msg[:len(self.frame_opt.access_path)] != self.frame_opt.access_path:
                result.code = MCCodeDefines.AccessPathError
                return result
            msg = msg[len(self.frame_opt.access_path):]

            # 存在响应数据时解析响应数据
            if res_code == b'\x02':
                check_sum = self.check_sum(msg[:-2])

                if check_sum is False:
                    if msg[-1:] != b'\x03':
                        result.code = MCCodeDefines.ControlCodeError
                        return result
                    msg = msg[:-1]
                else:
                    if msg[-3:-2] != b'\x03':
                        result.code = MCCodeDefines.ControlCodeError
                        return result
                    if msg[-2:] != check_sum:
                        result.code = MCCodeDefines.CheckSumError
                        return result
                    msg = msg[:-3]

                # 数据解析
                result.is_success = True
                result.code = MCCodeDefines.NormalFinish
                result.data = ascii_to_value(msg, length)
                return result

            elif res_code == b'\x06':
                result.is_success = True
                result.code = MCCodeDefines.NormalFinish
                return result

            elif res_code == b'\x15':
                result.code = MCCodeDefines.AbnormalFinish
                result.data = str(msg)
                return result

        else:
            result.code = MCCodeDefines.ControlCodeError
            return result


MCFormat = [None, MCForm1, MCForm2, MCForm3, MCForm4]


class MCNet4C:
    def __init__(self, host, port, timeout=1,
                 form=4, check_sum=False, slave_id=0, net_id=0, controller_id=0xFF,
                 target_io_id_high=0x03, target_io_id_low=0xFF, target_id=0, visitor_id=0):
        if form not in (1, 2, 3, 4):
            raise ValueError("Invalid format option!")

        # 创建对应帧的解析模型
        frame_opt = FrameOptionQ4C(slave_id=slave_id, net_id=net_id, controller_id=controller_id,
                                   target_io_id_high=target_io_id_high, target_io_id_low=target_io_id_low,
                                   target_id=target_id, visitor_id=visitor_id)

        # 选择对应的格式，生成协议生成器
        self.protocol_creator = MCFormat[form](frame_opt=frame_opt, check=check_sum)

        # 创建网络通讯接口
        self.host = host
        self.port = port
        self.interface = None
        self.connected = False
        self.timeout = timeout
        # self.connect()

    def connect(self):
        self.interface = socket.socket()
        self.interface.settimeout(self.timeout)
        self.interface.connect((self.host, self.port))
        self.connected = True

    def disconnect(self):
        self.interface.close()
        self.connected = False

    def recv(self, size=1024):
        return self.interface.recv(size)

    def send(self, data):
        self.interface.send(data)

    def GetX(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_x(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), BIT)

    def GetY(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_y(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), BIT)

    def GetM(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_m(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), BIT)

    def GetD(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_d(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), WORD)

    def GetL(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_l(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), BIT)

    def GetW(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_w(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), WORD)

    def GetR(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_r(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), WORD)

    def GetDD(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_d(addr, size * 2)))
        return self.protocol_creator.analyze_msg(self.recv(), DWORD)

    def GetF(self, addr, size):
        self.send(self.protocol_creator.request_msg(self.protocol_creator.frame_opt.read_f(addr, size)))
        return self.protocol_creator.analyze_msg(self.recv(), BIT)


class MCNetFrame:
    def __init__(self, host, port, timeout=1,
                 frame='3E', serial_num=0,
                 net_id=b'00', ctrl_id=b'FF',
                 target_io_h=b'03', target_io_l=b'FF',
                 module_id=b'00', timer: int = 0):
        # 4E帧特殊序列号
        self.serial_num = '{:04d}'.format(serial_num).encode('ascii')
        # 副帧头
        if frame == '4E':
            self.sub_header = b'5400' + self.serial_num + b'0000'
            self.req_sub_header = b'D400' + self.serial_num + b'0000'
        elif frame == '3E':
            self.sub_header = b'5000'
            self.req_sub_header = b'D000'
        else:
            raise TypeError('Frame type must be 4E or 3E!')

        # 访问路径
        self.visit_path = net_id + ctrl_id + target_io_h + target_io_l + module_id

        # 监视定时器
        self.timer = '{:04d}'.format(int(timer / 0.25)).encode('ascii')

        # 数据请求指令
        self.read_words_order = b'04010000'
        self.read_bits_order = b'04010001'
        self.write_words_order = b'14010000'
        self.write_bits_order = b'14010001'

        # 软元件编号
        self.X = b'X*'
        self.Y = b'Y*'
        self.M = b'M*'
        self.D = b'D*'
        self.L = b'L*'
        self.W = b'W*'
        self.R = b'R*'
        self.F = b'F*'
        # 创建网络通讯接口
        self.host = host
        self.port = port
        self.interface = None
        self.connected = False
        self.timeout = timeout
        self.connect()

    def connect(self):
        self.interface = socket.socket()
        self.interface.settimeout(self.timeout)
        self.interface.connect((self.host, self.port))
        self.connected = True

    def disconnect(self):
        self.interface.close()
        self.connected = False

    def recv(self, size=1024):
        return self.interface.recv(size)

    def send(self, data):
        self.interface.send(data)

    # 通讯请求帧生成器
    def generate_request_frames(self, option):
        tail_data = self.timer + option
        data_len = '{:04X}'.format(len(tail_data)).encode('ascii')
        return self.sub_header + self.visit_path + data_len + tail_data

    # 通讯返回帧解析器
    def analyze_msg(self, msg, length):
        result = OperateResult()

        sub_len = len(self.sub_header)
        # 获取返回帧头
        _sub_header = msg[:sub_len]
        if _sub_header != self.req_sub_header:
            result.code = MCCodeDefines.SubHeaderError
            return result
        msg = msg[sub_len:]
        # 获取返回帧路径
        vpath_len = len(self.visit_path)
        _visit_path = msg[:vpath_len]
        if _visit_path != self.visit_path:
            result.code = MCCodeDefines.AccessPathError
            return result
        msg = msg[vpath_len:]
        # 获取响应数据长度
        req_len = int(msg[:4], 16)
        msg = msg[4:]
        # 获取返回帧的结束代码
        finish_code = msg[:4]
        # 开始校验数据
        if req_len != len(msg):
            result.code = MCCodeDefines.DataLengthError
            return result
        elif req_len == 4:
            # 当结束代码为0时代表数据正常处理结束
            if int(finish_code) == 0:
                result.is_success = True
                return result
            else:
                result.data = int(finish_code)
                result.code = MCCodeDefines.FinishCodeError
                return result

        # 正确数据解析
        msg = msg[4:]
        result.is_success = True
        result.code = MCCodeDefines.NormalFinish
        result.data = ascii_to_value(msg, length)
        return result

    # 获取操作目标寄存器代码
    def read_x(self, addr, size):
        return self.read_bits_order + self.X + '{:06X}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_y(self, addr, size):
        return self.read_bits_order + self.Y + '{:06X}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_m(self, addr, size):
        return self.read_bits_order + self.M + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_d(self, addr, size):
        return self.read_words_order + self.D + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_l(self, addr, size):
        return self.read_bits_order + self.L + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_w(self, addr, size):
        return self.read_words_order + self.W + '{:06X}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_r(self, addr, size):
        return self.read_words_order + self.R + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def read_f(self, addr, size):
        return self.read_bits_order + self.F + '{:06d}'.format(addr).encode('ascii') \
               + '{:04X}'.format(size).encode('ascii')

    def GetX(self, addr, size):
        self.send(self.generate_request_frames(self.read_x(addr, size)))
        return self.analyze_msg(self.recv(), BIT)

    def GetY(self, addr, size):
        self.send(self.generate_request_frames(self.read_y(addr, size)))
        return self.analyze_msg(self.recv(), BIT)

    def GetM(self, addr, size):
        self.send(self.generate_request_frames(self.read_m(addr, size)))
        return self.analyze_msg(self.recv(), BIT)

    def GetD(self, addr, size):
        self.send(self.generate_request_frames(self.read_d(addr, size)))
        return self.analyze_msg(self.recv(), WORD)

    def GetL(self, addr, size):
        self.send(self.generate_request_frames(self.read_l(addr, size)))
        return self.analyze_msg(self.recv(), BIT)

    def GetW(self, addr, size):
        self.send(self.generate_request_frames(self.read_w(addr, size)))
        return self.analyze_msg(self.recv(), WORD)

    def GetR(self, addr, size):
        self.send(self.generate_request_frames(self.read_r(addr, size)))
        return self.analyze_msg(self.recv(), WORD)

    def GetF(self, addr, size):
        self.send(self.generate_request_frames(self.read_f(addr, size)))
        return self.analyze_msg(self.recv(), BIT)


class MCNet3E(MCNetFrame):
    def __init__(self, host, port, timeout=1,
                 net_id=b'00', ctrl_id=b'FF',
                 target_io_h=b'03', target_io_l=b'FF',
                 module_id=b'00', timer: int = 0):
        super().__init__(host=host, port=port,
                         timeout=timeout,
                         frame='3E', net_id=net_id, ctrl_id=ctrl_id,
                         target_io_h=target_io_h, target_io_l=target_io_l,
                         module_id=module_id, timer=timer)


class MCNet4E(MCNetFrame):
    def __init__(self, host, port, timeout=1,
                 serial_num=0,
                 net_id=b'00', ctrl_id=b'FF',
                 target_io_h=b'03', target_io_l=b'FF',
                 module_id=b'00', timer: int = 0):
        super().__init__(host=host, port=port,
                         timeout=timeout,
                         frame='4E', serial_num=serial_num,
                         net_id=net_id, ctrl_id=ctrl_id,
                         target_io_h=target_io_h, target_io_l=target_io_l,
                         module_id=module_id, timer=timer)
